#include <assert.h>
#include "STFQNWScheduler.h"
#include <cstdio>

STFQNWScheduler::STFQNWScheduler(long bytesPerSec_) 
 : bufsByStartTag (HeadComparator(true))
{
  // state -- global 
  bytesPerSec = bytesPerSec_;
  virtualTime = 0;
  maxFinishTag = 0;
  currentFid = 0;
  delay = 0;
  delayChanged = false;
  bufWaiting = 0;

  // synch
  smutex_init(&mutex);
  scond_init(&waitMyTag);
  scond_init(&waitTimeChanged);
}

/**
  map<fid,deque<sftags> > fidToBufToTags;
  map<fid,int>            fidToBufCount;
*/

void 
STFQNWScheduler::waitMyTurn(int flowId, float weight, int lenToSend)
{
  smutex_lock(&mutex);
  t_time starttag;
  t_time finishtag;

  if (!bufWaiting) {
    virtualTime = getMaxFinishTag();
  }

  int c_bufcount = fidToBufCount[flowId];

  // resizing backend storage of metadata
  if (c_bufcount >= (int)fidToBufToTags[flowId].max_size()) {
    fidToBufToTags[flowId].resize(2*c_bufcount);
  }

  // give a start tag to this flow
  if (c_bufcount == 0) {
    // if new flow, max(vtime,0), and vtime is always >= 0;
    starttag = virtualTime;
  }
  else {
    // if old flow, max("finish_tag of flowId, prev_buf_id", vtime)
		starttag = max(fidToBufToTags[flowId][c_bufcount-1].second, virtualTime);
  }
	sftags tagpair (0,0);
	fidToBufToTags[flowId].push_back(tagpair);
	fidToBufToTags[flowId][c_bufcount].first = starttag;

  // denormalize for fast lookup
	tagfid foo (starttag, flowId);
	bufsByStartTag.push(foo);

  // give a finish tag
  finishtag = starttag + lenToSend/weight;
  fidToBufToTags[flowId][c_bufcount].second = finishtag;

  // actually wait until turn
  while (currentFid != flowId) {
    bufWaiting++;
    scond_wait(&waitMyTag, &mutex);
    bufWaiting--;
  }
  currentFid = -1;

  // calc delay
  delay = 1000 * (lenToSend / bytesPerSec);
  delayChanged = true;
  scond_signal(&waitTimeChanged, &mutex);

  fidToBufCount[flowId]++;

  smutex_unlock(&mutex);
}

long long 
STFQNWScheduler::signalNextDeadline(long long prevDeadline)
{
  smutex_lock(&mutex);

  // find which buf should send next and run him

  while (!delayChanged) {
		if (!bufsByStartTag.empty()) {
			currentFid = bufsByStartTag.top().second; // bBST.top() => pair of (starttag, fid)
			bufsByStartTag.pop();
		}
    scond_broadcast(&waitMyTag, &mutex);
    scond_wait(&waitTimeChanged, &mutex);
  }
  delayChanged = false;

  smutex_unlock(&mutex);
  return prevDeadline + delay;
}

t_time
STFQNWScheduler::getMaxFinishTag()
{
  t_time max = virtualTime;
  map<fid,deque<sftags> >::iterator iter = fidToBufToTags.begin();
  for (; iter != fidToBufToTags.end(); ++iter) {
    t_time finishflow = (*iter).second.back().second;
    max = (max > finishflow ? max : finishflow);
  }
  return max;
}
